#include "kobj.h"

#include <sstream>
#include <cmath>

k_int KObject::ToInt() const
{
	switch (this->Type)
	{
	case K_INT:
		return this->v_int;
	case K_REAL:
		return (k_int)this->v_real;
	case K_STRING:
	default:
		k_int result;
		{
			std::istringstream is(this->v_string);
			if (!(is >> result))
				result = (k_int)0;
		}
		return result;
	}
}

k_real KObject::ToReal() const
{
	switch (this->Type)
	{
	case K_INT:
		return (k_real)this->v_int;
	case K_REAL:
		return this->v_real;
	case K_STRING:
	default:
		k_real result;
		{
			std::istringstream is(this->v_string);
			if (!(is >> result))
				result = (k_real)0;
		}
		return result;
	}
}

k_string KObject::ToString() const
{
	k_string result;
	switch (this->Type)
	{
	case K_INT:
		{
			std::ostringstream os;
			os << this->v_int;
			result = os.str();
		}
		break;
	case K_REAL:
		{
			std::ostringstream os;
			os << this->v_real;
			result = os.str();
		}
		break;
	case K_STRING:
	default:
		result = this->v_string;
		break;
	}

	return result;
}

KObject::operator bool() const
{
	switch (this->Type)
	{
	case K_INT:
		return this->v_int != (k_int)0;
	case K_REAL:
		return this->v_real != (k_real)0;
	case K_STRING:
	default:
		return this->v_string.length() != 0;
	}
}

KObject& KObject::operator =(const KObject &rhs)
{
	this->Type = rhs.Type;
	switch (rhs.Type)
	{
	case K_INT:
		this->v_int = rhs.v_int;
		break;
	case K_REAL:
		this->v_real = rhs.v_real;
		break;
	case K_STRING:
		this->v_string = rhs.v_string;
		break;
	}

	return *this;
}

KObject KObject::operator +(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() + rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() + rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator -(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() - rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() - rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator -() const
{
	switch (this->Type)
	{
	case K_INT:
		return KObject(-this->v_int);
	case K_REAL:
		return KObject(-this->v_real);
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator *(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() * rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() * rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator /(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() / rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() / rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator %(const KObject &rhs) const
{
	// ASSUMED: checked for string type already

	return KObject(this->ToInt() % rhs.ToInt());
}

KObject KObject::operator ==(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() == rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() == rhs.ToReal());
	default:
		return KObject(this->ToString().compare(rhs.ToString()) == 0);
	}
}

KObject KObject::operator !=(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() != rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() != rhs.ToReal());
	default:
		return KObject(this->ToString().compare(rhs.ToString()) != 0);
	}
}

KObject KObject::operator <(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() < rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() < rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator <=(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() <= rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() <= rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator >(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() > rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() > rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator >=(const KObject &rhs) const
{
	switch (KObject::HigherType(this->Type, rhs.Type))
	{
	case K_INT:
		return KObject(this->ToInt() >= rhs.ToInt());
	case K_REAL:
		return KObject(this->ToReal() >= rhs.ToReal());
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::operator &&(const KObject &rhs) const
{
	return KObject((bool)(*this) && (bool)rhs);
}

KObject KObject::operator ||(const KObject &rhs) const
{
	return KObject((bool(*this) || (bool)rhs));
}

KObject KObject::operator ^(const KObject &rhs) const
{
	if ((bool)(*this) == (bool)rhs)
		return KObject(false);
	else
		return KObject(false);
}

KObject KObject::operator !() const
{
	return KObject(!(bool)(*this));
}

KObject KObject::IntDiv(const KObject &lhs, const KObject &rhs)
{
	// ASSUMED: checked for string type already

	return KObject(lhs.ToInt() / rhs.ToInt());
}

KObject KObject::Pow(const KObject &lhs, const KObject &rhs)
{
	switch (KObject::HigherType(lhs.Type, rhs.Type))
	{
	case K_INT:
		return KObject((k_int)pow((double)lhs.ToInt(), (double)rhs.ToInt()));
	case K_REAL:
		return KObject((k_real)pow((double)lhs.ToReal(), (double)rhs.ToReal()));
	default:
		return KObject((k_int)0);
	}
}

KObject KObject::Concat(const KObject &lhs, const KObject &rhs)
{
	k_string result(lhs.ToString());
	result += rhs.ToString();
	return result;
}

std::ostream& operator <<(std::ostream &os, const KObject& obj)
{
	switch (obj.Type)
	{
	case K_INT:
		os << obj.v_int;
		break;
	case K_REAL:
		os << obj.v_real;
		break;
	case K_STRING:
		os << obj.v_string;
		break;
	}

	return os;
}
